#include "Cubemap.h"
#include "FloatTextureEncoding.h"

namespace FinalRendering
{
	namespace Engine
	{
		Cubemap::Cubemap(Stream * stream)
		{
			stream->ReadBuffer(&resolution, 1);
			stream->ReadBuffer(&type, 1);
			stream->ReadBuffer(&internalType, 1);
			glGenTextures(1, &handle);
			glBindTexture(GL_TEXTURE_CUBE_MAP, handle);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
			RefPtr<unsigned int, gxArrayRefDisposer> pix;
			Array<float, true> decode;
			for (int i=0; i<6; i++)
			{
				if (type == Color)
				{
					if (internalType == GL_RGB9_E5 || internalType == GL_R11F_G11F_B10F ||
						internalType == GL_RGB16F || internalType == GL_RGB32F)
					{
						pix = new unsigned int[resolution*resolution];
						stream->ReadBuffer(pix.operator->(), resolution*resolution);
						DecodeData(pix.operator->(), decode, resolution,resolution);
						glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, internalType, resolution, resolution, 0, GL_RGBA,
							GL_FLOAT, decode.Buffer());
					}
					else if (internalType == GL_RGB || internalType == GL_RGBA)
					{
						pix = new unsigned int[resolution*resolution];
						stream->ReadBuffer(pix.operator->(), resolution*resolution);
						glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, internalType, resolution, resolution, 0, GL_RGBA,
							GL_UNSIGNED_BYTE, pix.operator->());
					}
					else
					{
						glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, internalType, resolution, resolution, 0, GL_RGBA,
							GL_UNSIGNED_BYTE, 0);
						throw L"Internal format not supported.";
					}
				}
				else
					glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_DEPTH_COMPONENT, resolution, resolution, 0, GL_DEPTH_COMPONENT,
						GL_INT, 0);
			}
		}

		void Cubemap::SaveToStream(Stream * stream)
		{
			stream->WriteBuffer(&resolution, 1);
			stream->WriteBuffer(&type, 1);
			stream->WriteBuffer(&internalType, 1);
			glBindTexture(GL_TEXTURE_CUBE_MAP, handle);
			Array<float, true> floatPixels;
			Array<unsigned int, true> intPixels;
			for (int i=0; i<6; i++)
			{
				if (internalType != GL_RGB && internalType != GL_RGBA)
				{
					floatPixels.SetSize(resolution*resolution*3);
					glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGB, GL_FLOAT, floatPixels.Buffer());
					EncodeData(floatPixels.Buffer(), intPixels, resolution, resolution); 
					stream->WriteBuffer(intPixels.Buffer(), intPixels.Count());
				}
				else
				{
					intPixels.SetSize(resolution*resolution);
					glGetTexImage(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_RGBA, GL_UNSIGNED_BYTE, intPixels.Buffer());
					stream->WriteBuffer(intPixels.Buffer(), intPixels.Count());
				}
			}
		}

		Cubemap::Cubemap(int _res, Cubemap::CubemapType _type, int _internalType)
			:resolution(_res), type(_type), internalType(_internalType)
		{
			glGenTextures(1, &handle);
			glBindTexture(GL_TEXTURE_CUBE_MAP, handle);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
			for (int i=0; i<6; i++)
			{
				if (type == Color)
					glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, internalType, resolution, resolution, 0, GL_RGBA,
						GL_UNSIGNED_BYTE, 0);
				else
					glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, 0, GL_DEPTH_COMPONENT, resolution, resolution, 0, GL_DEPTH_COMPONENT,
						GL_INT, 0);
			}
		}

		Cubemap::~Cubemap()
		{
			glDeleteTextures(1, &handle);
		}

		int Cubemap::GetResolution()
		{
			return resolution;
		}

		Cubemap::CubemapType Cubemap::GetCubemapType()
		{
			return type;
		}

		void Cubemap::Bind()
		{
			glBindTexture(GL_TEXTURE_CUBE_MAP, handle);
		}

		void Cubemap::Render(Event<> * renderFunc, const vec3 & pos)
		{
			if (!fbo)
				fbo = new FrameBufferObject();
			if (!rb)
			{
				if (type == Color)
				{
					rb = new RenderBuffer(GL_DEPTH_COMPONENT, resolution, resolution);
					fbo->AttachRenderBuffer(GL_DEPTH_ATTACHMENT, rb.operator ->());
				}
				else
				{
					rb = new RenderBuffer(GL_RGBA, resolution, resolution);
					fbo->AttachRenderBuffer(GL_COLOR_ATTACHMENT0, rb.operator ->());
				}				
			}
			
			glPushAttrib(GL_VIEWPORT_BIT);
			glViewport(0,0,resolution,resolution);
			glMatrixMode(GL_PROJECTION);
			{
				glPushMatrix();
				glLoadIdentity();
				gluPerspective(90, 1.0f, 1.0f, 10000.0f);
			}
			glMatrixMode(GL_MODELVIEW);
			
			glPushMatrix();

			glBindTexture(GL_TEXTURE_CUBE_MAP, handle);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,	GL_NEAREST);
			glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

			for (int i=0; i<6; i++)
			{
				if (type == Color)
					fbo->AttachTexture(GL_COLOR_ATTACHMENT0, handle, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i);
				else
					fbo->AttachTexture(GL_DEPTH_ATTACHMENT, handle, GL_TEXTURE_CUBE_MAP_POSITIVE_X+i);
				bool consistency = fbo->IsReady();
				fbo->Bind();
				glEnable(GL_CULL_FACE);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
				glLoadIdentity();
				switch (i)
				{ 
				case 0:
					glRotatef(90.0f,0.0f,1.0f,0.0f); 
					glRotatef(180.0f,1.0f,0.0f,0.0f);
					break;
				case 1:
					glRotatef(-90.0f,0.0f,1.0f,0.0f);
					glRotatef(180.0f,1.0f,0.0f,0.0f);
					break;
				case 2:
					glRotatef(-90.0f,1.0f,0.0f,0.0f);
					break;
				case 3:
					glRotatef(90.0f,1.0f,0.0f,0.0f);
					break;
				case 4:
					glRotatef(180.0f,1.0f,0.0f,0.0f);
					break;
				case 5:
					glRotatef(180.0f,0.0f,0.0f,1.0f);
					break;
				}	
				glTranslatef(-pos.x,-pos.y,-pos.z);
				renderFunc->Invoke();
				fbo->UnBind();

			}
			glPopMatrix();
			
			glPopAttrib();

			glMatrixMode(GL_PROJECTION);
			glPopMatrix();
			glMatrixMode(GL_MODELVIEW);
		}
		
	}
}